#include <iostream>
#include <math.h>
#include <ctime>
#include "LightManager.h"
#include "Trigon.h"
#include "CamHandler.h"
#include "LightHandler.h"
#include "Clip.h"
#include "hud.h"
#include "Glas.h"
#include "schrank.h"
//#include "Manager/SoundThread.h"
#include <osg/Texture2D>
#include <osg/Material>
#include <osg/PositionAttitudeTransform>
#include <osgDB/ReadFile>
#include <osg/MatrixTransform>
#include <osg/ShapeDrawable>
#include <osgViewer/Viewer>
#include <osg/Camera>
#include <osg/Geode>
#include <osg/Viewport>
#include <osg/Drawable>
#include <osg/Viewport>
#include <osgViewer/CompositeViewer>
#include <osgViewer/Viewer>
#include <osgGA/TrackballManipulator>
#include <osgText/Text>
#include <osg/PositionAttitudeTransform>
#include <osg/AnimationPath>
#include <osg/Fog>

#ifdef __linux__
	#define RESOURCEPATH "/res/"

#elif _WIN32
	#include <Windows.h>
	#define RESOURCEPATH "\\Release\\res\\"
#endif

using namespace osg;
using namespace osgViewer;
using namespace std;

osg::ref_ptr<osg::Group> root;
osg::ref_ptr<osg::AnimationPathCallback> apcb;
osg::ref_ptr<osg::Fog> fog;
osgViewer::Viewer viewer;
LightManager* lightManager;
Clip* clip;
HUD * hud;
Glas* glas;
osg::Vec3d _eye = osg::Vec3d(2.5f,150.0f,11.0f);
osg::Vec3d _center = osg::Vec3d(2.5f, 150.0f, 11.0f);
osg:: Vec3d _up =  osg::Vec3d(0.0,0.0,1.0);
int height, width;


#define RESOURCES 18
string executable;
string res[RESOURCES];

/*
 * Schneidet den Dateinamen aus einer Pfadangabe ab
 */

string getPath(const string& str){
	size_t found;
	found=str.find_last_of("/\\");
	return str.substr(0,found);
}

/*
 * Methode um die vollstaendigen Resourcenpfade
 * plattformuebergreifend zu erstellen.
 */

void initResources(){
	#ifdef __linux__
		string path = getPath(executable);
		path = getPath(path);

	#elif _WIN32
		char l_name[ MAX_PATH + 1 ];
		l_name[ sizeof( l_name ) -1] = 0;
		GetModuleFileName( NULL, l_name, sizeof( l_name ) -1 );
		string path = l_name;
		path = getPath(path);
		path = getPath(path);
	#endif

	for (int i = 0; i < RESOURCES; i++){
		res[i]+=path;
		res[i]+=RESOURCEPATH;
	}

	res[0]+="baseTextured.3DS";
	res[1]+="floorTextured.3DS";
	res[2]+="ceiling.3DS";
	res[3]+="barTextured.3DS";
	res[4]+="pillarTextured.3ds";
	res[5]+="DiscoKugelCenter.3ds";
	res[6]+="danceFloorTextured.3ds";
	res[7]+="parLights.3ds";
	res[8]+="mixpult.3DS";
	res[9]+="sofa.3DS";
	res[10]+="table.3DS";
	res[11]+="ubuntu.ttf";
	res[12]+="door.3DS";
	res[13]+="exitsign.3DS";
	res[14]+="boxes.3DS";
	res[15]+="mixplateLeft.3DS";
	res[16]+="mixplateRight.3DS";
	res[17]+="carbon-fibre.jpg";
}

/*
 * Rotiert ein Objekt um die Z-Achse
 */

osg::AnimationPath* createRotation(const float time, const Vec3d pos, const Vec3d axis)
{
	osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath;
	path->setLoopMode( osg::AnimationPath::LOOP );

	unsigned int numSamples = 36;
	float delta_time = time / (float)numSamples;
	for ( unsigned int i=0; i<numSamples; i++)
	{
		osg::Quat rot(osg::DegreesToRadians(float(i*10)), axis);
		path->insert( delta_time * (float)i, osg::AnimationPath::ControlPoint(pos, rot));
	}
	return path.release();
}


/** Skaliert eine Node anhand des uebergebenen Vektors **/
ref_ptr<PositionAttitudeTransform> scaleObject(const osg::ref_ptr<osg::Node> node,
		const Vec3d vector)
{
	osg::ref_ptr<osg::PositionAttitudeTransform> transform =
		new osg::PositionAttitudeTransform;
	transform->addChild(node.get());
	transform->setScale(vector);
	return transform;
}

/** Positioniert eine Node anhand der uebergebenen Koordinaten **/
ref_ptr<MatrixTransform> translateObject(const osg::ref_ptr<osg::Node> node,
		const Vec3d vector)
{
	osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform;
	transform->addChild(node.get());
	transform->setMatrix(osg::Matrix::translate(vector.x(), vector.y(), vector.z()));
	return transform;
}

/** Dreht eine Node anhand der uebergebenen Werte **/
ref_ptr<MatrixTransform> rotateObject(const osg::ref_ptr<osg::Node> node,
		const float angle, const Vec3d vector)
{
	osg::ref_ptr<osg::MatrixTransform> transform = new osg::MatrixTransform;
	transform->addChild(node.get());
	transform->setMatrix(osg::Matrix::rotate(angle, vector));
	return transform;
}

/** Initialisiert den OSG-Viewer **/
int createViewer(osg::Group* root) {

	//Erstellen des OSG-Viewers im 800x600 Fenstermodus
	viewer.setSceneData(root);
	viewer.setUpViewInWindow(100,100,width,height);

	//Setzen des Ausgangsblickwinkels
	viewer.getCamera()->setViewMatrixAsLookAt(_eye, _center, _up);
	viewer.realize();

	//Initialisieren des LightManagers und uebergeben der osg::Group root
	lightManager = new LightManager(root);

	//Eventhandler zum Abfangen von Tastatureingaben und zum Aendern des Lichts
	viewer.addEventHandler(new CamHandler(viewer.getCamera(), clip, hud, 
		_eye, _center, _up));
	viewer.addEventHandler(new LightHandler(lightManager, hud));	
	
	// Viewer-Schleife. Alle Updates waehrend der Laufzeit 
	// werden hier durchgefuehrt
	viewer.getCamera()->setAllowEventFocus(false);

	//Soundroutine: Thread starten, der Sound abspielt
	//SoundThread* _soundThread = new SoundThread();
	//_soundThread->start();

	// Viewer Schleife mit FPS Messung
	int frames = 0;
	ostringstream fps;
	time_t begin=time(NULL);
	time_t end;
	while (!viewer.done()) {
		viewer.frame();
		frames++;
		#ifndef NOFRAMELIMITER
		OpenThreads::Thread::microSleep(16666);
		#endif
		end=time(NULL);
		if ((end - begin) >= 1){
			fps << frames << " FPS";
			if (frames < 20)
				hud->getOsgText(3)->setColor(Vec4d(255, 0, 0, 1));
			else if (frames < 35)
				hud->getOsgText(3)->setColor(Vec4d(255, 255, 0, 1));
			else
				hud->getOsgText(3)->setColor(Vec4d(0, 255, 0, 1));
			hud->setText(3, fps.str());
			frames = 0;
			fps.clear();
			fps.str("");
			begin=time(NULL);
		}
	}
	return 0;
}

void createScene(){
	
	// Einlesen der einzelnen Modelle 
	osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(res[0]);
	osg::ref_ptr<osg::Node> ground = osgDB::readNodeFile(res[1]);
	osg::ref_ptr<osg::Node> ceiling = osgDB::readNodeFile(res[2]);
	osg::ref_ptr<osg::Node> bar = osgDB::readNodeFile(res[3]);
	osg::ref_ptr<osg::Node> pillar = osgDB::readNodeFile(res[4]);
	osg::ref_ptr<osg::Node> discoOrb = osgDB::readNodeFile(res[5]);
	osg::ref_ptr<osg::Node> discoFloor = osgDB::readNodeFile(res[6]);
	osg::ref_ptr<osg::Node> lights = osgDB::readNodeFile(res[7]);
	osg::ref_ptr<osg::Node> videomixer = osgDB::readNodeFile(res[8]);
	osg::ref_ptr<osg::Node> sofa = osgDB::readNodeFile(res[9]);
	osg::ref_ptr<osg::Node> table = osgDB::readNodeFile(res[10]);
	osg::ref_ptr<osg::Node> door = osgDB::readNodeFile(res[12]);
	osg::ref_ptr<osg::Node> exitsign = osgDB::readNodeFile(res[13]);
	osg::ref_ptr<osg::Node> boxes = osgDB::readNodeFile(res[14]);
	osg::ref_ptr<osg::Node> mixplateLeft = osgDB::readNodeFile(res[15]);
	osg::ref_ptr<osg::Node> mixplateRight = osgDB::readNodeFile(res[16]);

	//Positionieren der Decke
	osg::ref_ptr<osg::MatrixTransform> ceilingTransForm = translateObject(ceiling.get(), Vec3d(0.0f, 0.0f, 39.5f));

	//Skalieren und Positionieren der Bar hinten
	osg::ref_ptr<osg::PositionAttitudeTransform> barTransform = scaleObject(bar.get(), Vec3d(0.45f,0.45f,0.45f));
	osg::ref_ptr<osg::MatrixTransform> barTranslate = translateObject(barTransform.get(), Vec3d(0.0f, -101.0f, 0.5f));

	//Positionieren der vier Saeulen
	osg::ref_ptr<osg::MatrixTransform> pillarNWTrans = translateObject(pillar.get(), Vec3d(39.0f, 39.0f, 0.0f));
	osg::ref_ptr<osg::MatrixTransform> pillarNETrans = translateObject(pillar.get(), Vec3d(-39.0f, 39.0f, 0.0f));
	osg::ref_ptr<osg::MatrixTransform> pillarSETrans = translateObject(pillar.get(), Vec3d(39.0f, -39.0f, 0.0f));
	osg::ref_ptr<osg::MatrixTransform> pillarSWTrans = translateObject(pillar.get(), Vec3d(-39.0f, -39.0f, 0.0f));

	//Positionieren und rotieren der Discokugel
	osg::ref_ptr<osg::MatrixTransform> discoOrbTransform = translateObject(discoOrb.get(), Vec3d(0.0f, -17.0f, 33.5f));
	apcb = new osg::AnimationPathCallback;
	apcb->setAnimationPath( createRotation( 15.0f, Vec3d(0.0f, -17.0f, 33.5f), osg::Z_AXIS ));
	discoOrbTransform->setUpdateCallback( apcb.get() );

	//Positionieren und Skalieren des Mixpults
	osg::ref_ptr<PositionAttitudeTransform> mixpultTransform = scaleObject(videomixer.get(), Vec3d(1.2f, 1.2f, 1.2f));
	osg::ref_ptr<MatrixTransform> mixpultTranslate = translateObject(mixpultTransform.get(), Vec3d(0.0f, 60.0f, 5.0f));

	//Skalieren, Positionieren und Rotieren der Lichtanlagen
	osg::ref_ptr<PositionAttitudeTransform> scaleLights = scaleObject(lights.get(), Vec3d(0.1f, 0.1f, 0.1f));

	osg::ref_ptr<MatrixTransform> rotateLightsRightX = rotateObject(scaleLights.get(), 3.1, Vec3d(1.0f, 0.0f, 0.0f));
	osg::ref_ptr<MatrixTransform> rotateLightsRightZ = rotateObject(rotateLightsRightX.get(), -0.6, Vec3d(0.0f, 0.0f, 1.0f));
	osg::ref_ptr<MatrixTransform> translateLightsRight = translateObject(rotateLightsRightZ.get(), Vec3d(-35.0f, -50.5f, 33.0f));

	osg::ref_ptr<MatrixTransform> rotateLightsLeftX = rotateObject(scaleLights.get(), 3.1, Vec3d(1.0f, 0.0f, 0.0f));
	osg::ref_ptr<MatrixTransform> rotateLightsLeftZ = rotateObject(rotateLightsLeftX.get(), 0.7, Vec3d(0.0f, 0.0f, 1.0f));
	osg::ref_ptr<MatrixTransform> translateLightsLeft = translateObject(rotateLightsLeftZ.get(), Vec3d(35.0f, -45.5f, 33.0f));

	//Skalieren, Rotieren und Translatieren der Sofas
	osg::ref_ptr<osg::MatrixTransform> sofaTranslateTop = translateObject(sofa.get(), Vec3d(-4370.0f, -3250.0f, -509.0f));
	osg::ref_ptr<MatrixTransform> sofaRotateTop = rotateObject(sofaTranslateTop.get(), 3.1, Vec3d(0.0f, 0.0f, 1.0f));

	osg::ref_ptr<osg::MatrixTransform> sofaTranslateRight = translateObject(sofa.get(), Vec3d(-4565.0f, -3327.0f, -509.0f));
	osg::ref_ptr<MatrixTransform> sofaRotateRight = rotateObject(sofaTranslateRight.get(), 1.55, Vec3d(0.0f, 0.0f, 1.0f));

	//Skalieren und Translatieren des Tisches
	osg::ref_ptr<osg::MatrixTransform> tableTranslate = translateObject(table.get(), Vec3d(-66.0f, -245.0f, -4722.0f));

	//Translatieren der Tuer
	osg::ref_ptr<MatrixTransform> doorTranslate = translateObject(door.get(), Vec3d(0.0f, -15.0f, 0.0f));

	//Translatieren des Exit-Signs
	osg::ref_ptr<MatrixTransform> exitSignTranslate = translateObject(exitsign.get(), Vec3d(0.0f, 19.0f, 0.0f));

	//Translatieren der Boxen
	osg::ref_ptr<MatrixTransform> boxesTranslateLeft = translateObject(boxes.get(), Vec3d(70.0f, -165.0f, 0.0f));
	osg::ref_ptr<MatrixTransform> boxesTranslateRight = translateObject(boxes.get(), Vec3d(110.0f, -165.0f, 0.0f));

	//Skalieren, Translatieren und Rotieren des Kompositionskoerpers (Glas-Schrank)
	Schrank  schrank = Schrank(&res[17]);
	osg::ref_ptr<MatrixTransform> schrankRotate = rotateObject(schrank.get(), 3.1, Vec3d(0, 0, 1));
	osg::ref_ptr<PositionAttitudeTransform> schrankScale = scaleObject(schrankRotate.get(), Vec3d(0.4f, 0.4f, 0.4f));
	osg::ref_ptr<MatrixTransform> schrankTranslate = translateObject(schrankScale.get(), Vec3d(5.0f, -161.0f, 10.0f));


	//Hinzufuegen der Modelle zur osg::Group root
	root = new osg::Group;
	root->addChild( model.get() );
	root->addChild( ground.get() );
	root->addChild( barTranslate.get() );
	root->addChild( pillarSWTrans.get());
	root->addChild( pillarNWTrans.get() );
	root->addChild( pillarNETrans.get() );
	root->addChild( pillarSETrans.get() );
	root->addChild( discoOrbTransform.get() );
	root->addChild( discoFloor.get() );
	root->addChild(mixpultTranslate.get());
	root->addChild(translateLightsRight.get());
	root->addChild(translateLightsLeft.get());
	root->addChild(ceilingTransForm.get());	
	root->addChild(sofaRotateTop.get());	
	root->addChild(sofaRotateRight.get());
	root->addChild(tableTranslate.get());
	root->addChild(doorTranslate.get());	
	root->addChild(exitSignTranslate.get());
	root->addChild(boxesTranslateLeft.get());
	root->addChild(boxesTranslateRight.get());
	root->addChild(mixplateLeft.get());
	root->addChild(mixplateRight.get());
	root->addChild(schrankTranslate.get());

	//Platzieren von ein paar Gläsern
	root->addChild(translateObject(scaleObject(glas->get(), Vec3d(0.12, 0.12, 0.12)), Vec3d(-3.0f, -144.0f, 6.6f)));
	root->addChild(translateObject(scaleObject(glas->get(), Vec3d(0.12, 0.12, 0.12)), Vec3d(-5.0f, -145.0f, 6.6f)));
	root->addChild(translateObject(scaleObject(glas->get(), Vec3d(0.12, 0.12, 0.12)), Vec3d(6.0f, -143.0f, 6.6f)));
	root->addChild(translateObject(scaleObject(glas->get(), Vec3d(0.12, 0.12, 0.12)), Vec3d(-68.0f, -135.0f, 5.2f)));
	root->addChild(translateObject(scaleObject(glas->get(), Vec3d(0.12, 0.12, 0.12)), Vec3d(-63.0f, -137.0f, 5.2f)));

	//Normalen von skalierten Objekten wieder auf 1 normieren
	root->getOrCreateStateSet()->setMode(GL_RESCALE_NORMAL, StateAttribute::ON);

	// Nebel erzeugen
	fog = new osg::Fog;
	fog->setMode( osg::Fog::LINEAR );
	fog->setStart( 0.0f );
	fog->setEnd( 1000.0f );
	fog->setColor( osg::Vec4d(0.8f, 0.8f, 0.8f, 1.0f) );
	root->getOrCreateStateSet()->setAttributeAndModes( fog.get() );

	// HUD instanzieren
	hud = new HUD(root, width, height, res[11]);
	hud->setText(0, "DJAL");
		
	// Clip mit Raumgrenzen und HUD instanzieren
	clip = new Clip(-90.0f, 90.0f, -165.0f, 165.0f, 11.0f, 38.0f, hud);

	// Clipping-Koordinaten Übergeben
	clip->addBoundingSphere(barTranslate.get()->getBound());
	clip->addBoundingSphere(pillarSWTrans.get()->getBound());
	clip->addBoundingSphere(pillarNWTrans.get()->getBound());
	clip->addBoundingSphere(pillarNETrans.get()->getBound());
	clip->addBoundingSphere(pillarSETrans.get()->getBound());
	clip->addBoundingSphere(discoOrbTransform.get()->getBound());
	clip->addBoundingSphere(mixpultTranslate.get()->getBound());
	clip->addBoundingSphere(translateLightsRight.get()->getBound());
	clip->addBoundingSphere(translateLightsLeft.get()->getBound());	
	clip->addBoundingSphere(sofaRotateTop.get()->getBound());	
	clip->addBoundingSphere(sofaRotateRight.get()->getBound());	
	clip->addBoundingSphere(tableTranslate.get()->getBound());
	clip->addBoundingSphere(boxesTranslateLeft.get()->getBound());	
	clip->addBoundingSphere(boxesTranslateRight.get()->getBound());	
}

int main( int argc, char** argv ){
	executable = argv[0];
	initResources();

	if (argc == 3){
		width = atoi(argv[1]);
		height = atoi(argv[2]);
	}
	else {
		width = 800;
		height = 600;
	}

	//Erstellen der Szene
	createScene();
	createViewer(root.get());
	return 0;
}
